/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace Cobalt
{
	using Content.Pipeline;

	internal static class Helpers
	{

		public static void DisposeAndNull<T>( ref T obj )
		{
			IDisposable disp = obj as IDisposable;
			if( disp != null )
				disp.Dispose();

			obj = default( T );
		}

		public static bool ValueEquals<K, V>( this Dictionary<K, V> dict, K key, V value )
		{
			V v;
			if( !dict.TryGetValue( key, out v ) )
				return false;

			if( v == null )
			{
				return value == null;
			}
			else
			{
				return v.Equals( value );
			}
		}

		public static bool PathsEqual( string a, string b )
		{
			if( a == null || b == null )
				throw new ArgumentNullException();

			a = a.Replace( Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar );
			b = b.Replace( Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar );

			return string.Compare( a, b, StringComparison.OrdinalIgnoreCase ) == 0;
		}

		public struct Pair<A, B>
		{
			private A a;
			public A First
			{
				get { return a; }
				set { a = value; }
			}

			private B b;
			public B Second
			{
				get { return b; }
				set { b = value; }
			}

			public Pair( A first, B second )
			{
				this.a = first;
				this.b = second;
			}

			public override int GetHashCode()
			{
				if( a == null )
					return b != null ? b.GetHashCode() : 0;
				
				if( b == null )
					return a.GetHashCode();
				
				return a.GetHashCode() + b.GetHashCode();
			}

			public override bool Equals( object obj )
			{
				if( obj == null )
					return false;

				try
				{
					Pair<A, B> other = (Pair<A, B>)obj;

					if( a == null )
					{
						if( other.a != null )
							return false;

						if( b == null )
							return other.b == null;

						return b.Equals( other.b );
					}

					if( b == null )
					{
						if( other.b != null )
							return false;

						return a.Equals( other.a );
					}

					return a.Equals( other.a ) && b.Equals( other.b );
				}
				catch( InvalidCastException )
				{
					return false;
				}
			}

			internal Pair<B, A> Reverse()
			{
				return new Pair<B, A>( b, a );
			}
		}

		public static bool ListsEqual<T>( List<T> a, List<T> b )
		{
			if( a == null )
				return b == null;
			if( b == null )
				return false;

			if( a.Count != b.Count )
				return false;

			for( int i = 0; i < a.Count; i++ )
				if( !a[i].Equals( b[i] ) )
					return false;

			return true;
		}

		public static void Convert<T>( this List<T> list, Converter<T, T> conversion )
		{
			for( int i = 0; i < list.Count; i++ )
				list[i] = conversion( list[i] );
		}

		public static void Remap<T>( this List<T> list, Dictionary<T, T> remap )
		{
			for( int i = 0; i < list.Count; i++ )
				list[i] = remap[list[i]];
		}

		public static void RemoveNullEntries<T>( this List<T> list )
		{
			for( int i = list.Count - 1; i >= 0; i-- )
				if( list[i] == null )
					list.RemoveAt( i );
		}

		public static void RemoveDuplicateEntries<T>( this List<T> list )
		{
			for( int i = 0; i < list.Count; i++ )
			{
				for( int j = list.Count - 1; j > i; j-- )
				{
					if( list[i].Equals( list[j] ) )
						list.RemoveAt( j );
				}
			}
		}

		public static string GetCobaltFrameworkTypeString( string typeName )
		{
			return string.Format( "Cobalt.Framework.{0}, Cobalt.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", typeName );
		}

		public static bool Equals( Vector3 a, Vector3 b, float tol )
		{
			return
				Math.Abs( a.X - b.X ) <= tol &&
				Math.Abs( a.Y - b.Y ) <= tol &&
				Math.Abs( a.Z - b.Z ) <= tol;
		}

		public static bool IsPowerOf2( int num )
		{
			if( num < 0 )
				throw new ArgumentOutOfRangeException();

			return (num & (num - 1)) == 0;
		}

		public static int RoundUpToPowerOf2( int num )
		{
			if( num < 0 )
				throw new ArgumentOutOfRangeException();

			for( int i = 31; i >= 0; i-- )
			{
				int check = 1 << i;
				if( (check & num) != 0 )
				{
					//found the highest bit
					if( ((check - 1) & num) != 0 )
						//number was slightly greater than given pow2
						return check * 2;
					else
						return check;
				}
			}

			return 1;
		}

		public static T[][] CreateSquareArray<T>( int dim0, int dim1 )
		{
			T[][] ret = new T[dim0][];
			for( int i = 0; i < ret.Length; i++ )
				ret[i] = new T[dim1];
			return ret;
		}

		public static T[] ArraySubrange<T>( T[] src, int index, int count )
		{
			if( src == null )
				throw new ArgumentNullException();
			if( index < 0 || count < 0 || index + count > src.Length )
				throw new ArgumentOutOfRangeException();

			T[] ret = new T[count];

			if( count > 0 )
				Array.Copy( src, index, ret, 0, count );

			return ret;
		}

		public static T[] ArraySubrange<T>( T[] src, int index )
		{
			if( src == null )
				throw new ArgumentNullException();
			if( index < 0 || index > src.Length )
				throw new ArgumentOutOfRangeException();

			return ArraySubrange( src, index, src.Length - index );
		}

		public static int Max( int r, int g, int b )
		{
			return Math.Max( r, Math.Max( g, b ) );
		}

		public static float Clamp( float val, float min, float max )
		{
			if( val < min ) return min;
			if( val > max ) return max;
			return val;
		}

		public static int Clamp( int val, int min, int max )
		{
			if( val < min ) return min;
			if( val > max ) return max;
			return val;
		}

		public static float Saturate( float val )
		{
			return Clamp( val, 0, 1 );
		}

		public static float ExpandByte( byte b )
		{
			return (float)b * (1.0F / (float)0xFF);
		}

		public static byte PackFloat( float f )
		{
			f = Saturate( f );
			return (byte)(f * 0xFF);
		}

		public static void Swap<T>( ref T a, ref T b )
		{
			T tmp = a;
			a = b;
			b = tmp;
		}

		public static Dictionary<T, T> CollapseChainMappings<T>( this Dictionary<T, T> map )
		{
			Dictionary<T, T> newMap = new Dictionary<T, T>();

			foreach( KeyValuePair<T, T> pair in map )
			{
				T t, v = pair.Value;

				while( map.TryGetValue( v, out t ) )
					v = t;

				newMap[pair.Key] = v;
			}

			return newMap;
		}

		public static Dictionary<V, K> InvertMapping<K, V>( this Dictionary<K, V> map )
		{
			var ret = new Dictionary<V, K>( map.Count );

			foreach( var p in map )
				ret[p.Value] = p.Key;

			return ret;
		}

		public static string ReadToToken( this ContentTokenReader reader, string token )
		{
			StringBuilder sb = new StringBuilder();

			while( reader.ReadToken( TokenReadFlags.NoLineBreak ) != null )
			{
				if( reader.LastToken == token )
					break;

				sb.Append( reader.LastToken );
				sb.Append( " " );
			}

			return sb.ToString();
		}

		public static object ParsePropertyValue( ContentTokenReader reader, ContentIdentity identity )
		{
			reader.ReadExactToken( "=", TokenReadFlags.NoLineBreak );

			string type = reader.ReadRequiredToken( TokenReadFlags.NoLineBreak );
			reader.ReadExactToken( "(", TokenReadFlags.NoLineBreak );

			object ret;
			bool ateClosingParen = false;

			try
			{
				switch( type )
				{
				case "color":
					System.Drawing.Color cl = (System.Drawing.Color)System.Convert.ChangeType( reader.ReadToToken( ")" ), typeof( System.Drawing.Color ) );
					ret = new Vector4( cl.R, cl.G, cl.B, cl.A ) * new Vector4( 1.0F / 0xFF );
					ateClosingParen = true;
					break;

				case "texture":
					ret = new ExternalReference<TextureContent>( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ), identity );
					break;

				case "string":
				case "technique":
					ret = reader.ReadRequiredToken( TokenReadFlags.NoLineBreak );
					break;

				case "int":
					ret = int.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) );
					break;

				case "float":
				case "float1":
					ret = float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) );
					break;

				case "float2":
					ret = new Vector2(
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) )
						);
					break;

				case "float3":
					ret = new Vector3(
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) )
						);
					break;

				case "float4":
					ret = new Vector4(
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) ),
						float.Parse( reader.ReadRequiredToken( TokenReadFlags.NoLineBreak ) )
						);
					break;

				default:
					throw new InvalidContentException( "Unknown parameter type.", reader.LastTokenIdentity );
				}
			}
			catch( Exception ex )
			{
				throw new InvalidContentException( "Error parsing parameter value.", reader.LastTokenIdentity, ex );
			}

			if( !ateClosingParen )
				reader.ReadExactToken( ")", TokenReadFlags.NoLineBreak );

			return ret;
		}

		public static void CopyDictionary( OpaqueDataDictionary dest, OpaqueDataDictionary source )
		{
			foreach( KeyValuePair<string, object> pair in source )
				dest[pair.Key] = pair.Value;
		}

		public static void WriteTags( ContentWriter output, OpaqueDataDictionary tags )
		{
			output.Write( tags.Count );
			foreach( KeyValuePair<string, object> pair in tags )
			{
				output.WriteRawObject( pair.Key );
				output.WriteObject( pair.Value );
			}
		}

		public static void CopyStream( Stream dest, Stream source )
		{
			byte[] buf = new byte[16 * 1024];

			for( ; ; )
			{
				int cbRead = source.Read( buf, 0, buf.Length );
				
				if( cbRead == 0 )
					break;
			
				dest.Write( buf, 0, cbRead );
			}
		}

		private static string Base32Chars = "01234567" + "89ABCDEF" + "GHIJKLMN" + "OPRSTUVW";

		public static string Base32Encode( byte[] buf )
		{
			StringBuilder ret = new StringBuilder();
			
			for( int i = 0, b = 8; i < buf.Length; )
			{
				int n;

				if( b > 5 )
				{
					//there are 5 or more bits left in the current byte
					
					//grab the 5 highest bits
					n = buf[i] >> (b - 5);
					b -= 5;
				}
				else
				{
					//less than five bits in the current byte

					//shift the remaining bits up into the high part of the index
					n = buf[i] << (8 - b);
					
					i++;

					if( i < buf.Length )
					{
						//bits remain! snag 5 - b bits off the top of the next byte
						n |= buf[i] >> (8 - (5 - b));						
						b = (8 - (5 - b));
					}
				}

				ret.Append( Base32Chars[n & 0x1F] );
			}

			return ret.ToString();
		}
	}

}